package orchestration.businessprocess.execution;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import orchestration.businessprocess.model.BPArtifact;
import orchestration.businessprocess.model.BPNodeId;
import orchestration.businessprocess.model.BusinessProcessModel;
import orchestration.businessprocess.model.nodes.BPNode;
import orchestration.conditions.exceptions.CheckingConditionException;
import orchestration.persistence.exceptions.OrchestrationDAOException;
import orchestration.utils.PropertyUtils;
import android.util.Log;

/** 
 * A class that models a BusinessProcess Execution.
 * Need to be run in an {@link IBPExecutionContainter}, that will offer
 * adapted methods that will hide the context.
 * Most of the methods of this class are getters/setters.
 * @see IExecutionStrategy implementations for the execution algorithm
 * @author  carlo
 */
public class BPExecution{

	private static final String TAG="BPExecution";
	public static final String STATUS_NEW = "new";
	public static final String STATUS_NORMAL_EXECUTION = "";
	public static final String STATUS_LOOPING = "looping";
	public static final String STATUS_COMPLETED = "ended";
	
	/**
	 * @uml.property  name="container"
	 * @uml.associationEnd  
	 */
	private IBPExecutionContainter container;
	/**
	 * @uml.property  name="listener"
	 * @uml.associationEnd  
	 */
	private IBPExecutionListener listener;
	/**
	 * @uml.property  name="executionStrategy"
	 * @uml.associationEnd  
	 */
	private IExecutionStrategy executionStrategy;

	//State-> must be saved!
	/**
	 * @uml.property  name="id"
	 */
	private long id; // should not change 
	/**
	 * @uml.property  name="model"
	 * @uml.associationEnd  
	 */
	private BusinessProcessModel model; // should not change 
	/**
	 * @uml.property  name="status"
	 */
	private String status;//will change
	/**
	 * used to check when all predecessors of a JoinGateway
	 * have been visited. In order to work even with loops,
	 * when going over a JoinGateway we remove his predecessors.
	 * For this reason do not rely on it to know which nodes have
	 * been visited
	 */
	private HashSet<BPNode> loopAwareVisitedSet;//will change
	private ArrayList<BPNode> toVisit;//will change
	/**
	 * @uml.property  name="currentNode"
	 * @uml.associationEnd  
	 */
	private BPNode currentNode;  //will change
	//saved everyTime saveProperty() is called
	private HashMap<String, Property> properties;

	
	public BPExecution() {
		super();
		this.executionStrategy= new BPSerialDepthExecutionStrategy(this);//<-- create another execution strategy if you want to modify the execution algorithm
		this.loopAwareVisitedSet=new HashSet<BPNode>();
		this.toVisit= new ArrayList<BPNode>();
		this.properties=new HashMap<String, Property>();
	}
	
	public void init(BPNodeId currentNodeId, ArrayList<BPNodeId> toVisitIds, HashSet<BPNodeId> visitedIds){
		if(status.equals(STATUS_NEW)){
			this.status=STATUS_NORMAL_EXECUTION;
			toVisit.add(model.getStartNode());
		}else{
			this.currentNode=model.getNode(currentNodeId);
			for(BPNodeId tvId : toVisitIds)
				this.toVisit.add(model.getNode(tvId));
			for(BPNodeId vId : visitedIds)
				this.loopAwareVisitedSet.add(model.getNode(vId));
		}
	}
	/**
	 * @return
	 * @uml.property  name="model"
	 */
	public BusinessProcessModel getModel() {
		return model;
	}
	
	/**
	 * @param model
	 * @uml.property  name="model"
	 */
	public void setModel(BusinessProcessModel model) {
		this.model = model;
	}
	/**
	 * @return
	 * @uml.property  name="container"
	 */
	public IBPExecutionContainter getContainer() {
		return container;
	}
	/**
	 * @param container
	 * @uml.property  name="container"
	 */
	public void setContainer(IBPExecutionContainter container) {
		this.container = container;
	}
	
	/**
	 * @param id
	 * @uml.property  name="id"
	 */
	public void setId(long id) {
		this.id = id;
	}

	/**
	 * @return
	 * @uml.property  name="id"
	 */
	public long getId() {
		return id;
	}
	public void setExecutionStatus(String executionStatus) {
		this.status = executionStatus;
	}
	public String getExecutionStatus() {
		return status;
	}
	public ArrayList<BPNode> getNodesToVisit() {
		return toVisit;
	}
	public HashSet<BPNode> getVisited() {
		return loopAwareVisitedSet;
	}
	/**
	 * @return
	 * @uml.property  name="currentNode"
	 */
	public BPNode getCurrentNode() {
		return currentNode;
	}
	/**
	 * @param currentNode
	 * @uml.property  name="currentNode"
	 */
	public void setCurrentNode(BPNode currentNode) {
		this.currentNode = currentNode;
	}

	/**
	 * @param listener
	 * @uml.property  name="listener"
	 */
	public void setListener(IBPExecutionListener listener) {
		this.listener = listener;
	}
	/**
	 * @return
	 * @uml.property  name="listener"
	 */
	public IBPExecutionListener getListener() {
		return listener;
	}
	
	public void resume(boolean repeatLastTaskOrEvent){
		if(!status.equals(STATUS_COMPLETED)){
			if(repeatLastTaskOrEvent && currentNode!=null){
				//visit last again last task or event
				executionStrategy.resumeFromCurrent();
			}else{
				executionStrategy.resumeFromNext();
			}
		}
	}
	
	public Property getProperty(String artifactName) {
		try {
			Property property = properties.get(artifactName);
				if(property==null){
					property=loadProperty(artifactName);
					properties.put(artifactName, property);
				}
			return property;
		} catch (OrchestrationDAOException e) {
			getContainer().handleException(e);
			return null;
		}
	}

	private Property loadProperty(String bpaName) throws OrchestrationDAOException {
		Property pr=null;
		BusinessProcessModel bpm =model;
		BPArtifact artifact = bpm.getArtifactByName(bpaName);
		pr = container.getPropertyDAO().getProperty(id, artifact);
		if(pr==null){
			pr=artifact.createProperty();
			saveProperty(pr);
		}
			return pr;
	}

	public void saveProperty(Property property) {

		Log.i(TAG,"saving property " + property.getName() + " "+ PropertyUtils.getStringValue(property));
		this.properties.put(property.getName(), property);
		try {
			container.getPropertyDAO().saveProperty(property, id);
		} catch (OrchestrationDAOException e) {
			container.handleException(e);
		}
	}
	
	boolean evaluateExpr(String expression, Set<BPArtifact> input){
		List<Property> inputProperties= new ArrayList<Property>();
		if(input!=null)
			for(BPArtifact bpa : input)
				inputProperties.add(getProperty(bpa.getName()));
		try {
			return container.getConditionEvaluator().check(expression, inputProperties);
		} catch (CheckingConditionException e) {
			container.handleException(e);
		}
		return false;
	}
}
